이론이 실제 코드로 어떻게 구현되는지 프로젝트 구조를 통해 알아봅니다.
이 파일은 '데이터 파일로부터 딱 한 줄의 문장을 가져와, 모델이 먹을 수 있는 숫자 배열로 만드는 법'을 정의합니다. `torch.utils.data.Dataset`을 상속받아 우리만의 데이터셋 규칙을 만듭니다.
초기화 (__init__)
: 데이터 파일(`ratings_train.tsv` 등)을 불러와 메모리에 올리고,
단어와 고유 번호(ID)가 매핑된 단어 사전(vocab)을 준비하는 등 최초 1회만 실행되는 준비 작업을 합니다.
데이터 길이 (__len__)
: 전체 데이터가 총 몇 개인지 알려주는 역할을 합니다.
데이터 처리 (__getitem__)
: `idx(인덱스)`번째 데이터를 실제로 처리하는 핵심 로직입니다.
문장을 단어 단위로 쪼개고(토큰화), 단어 사전을 참고해 단어를 숫자 ID 배열로 변환한 뒤 텐서 형태로 반환합니다.
임베딩 벡터는 여기서 만들지 않습니다.
# text_classification/dataset.py
import torch
from torch.utils.data import Dataset
class TextClassificationDataset(Dataset):
def __init__(self, file_path, vocab):
self.data = self.load_data(file_path)
self.vocab = vocab
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
sentence, label = self.data[idx]
tokens = self.tokenize(sentence)
token_ids = [self.vocab.get(token, self.vocab['<unk>']) for token in tokens]
return torch.tensor(token_ids), torch.tensor(label)
이론에서 봤던 Embedding Layer → RNN → Linear Layer 구조가 그대로 코드로 나타납니다.
`torch.nn.Module`을 상속받아 모델의 뼈대를 구성합니다.
부품 정의 (__init__)
: 모델에 필요한 부품(레이어)들을 미리 정의합니다.
바로 여기서 `nn.Embedding` 레이어를 선언하여, `DataLoader`가 배달해 준 '정수 ID 배열'을 받아
'임베딩 벡터'로 변환할 준비를 합니다. 이 레이어의 가중치는 학습 과정에서 계속 업데이트됩니다.
데이터 흐름 정의 (forward)
: 데이터가 모델을 통과하는 실제 흐름을 정의합니다.
정수 ID 배열이 임베딩 레이어를 통과해 벡터 시퀀스가 되고, 이어서 RNN 레이어와 Linear 레이어를 차례로 거쳐
최종 예측값(Logits)이 나오는 과정을 코드로 작성합니다.
# text_classification/models/rnn.py
import torch.nn as nn
class RNNClassifier(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_size, num_classes):
super().__init__()
self.embedding_layer = nn.Embedding(vocab_size, embedding_dim)
self.rnn_layer = nn.RNN(
input_size=embedding_dim,
hidden_size=hidden_size,
batch_first=True,
bidirectional=True
)
self.linear_layer = nn.Linear(hidden_size * 2, num_classes)
def forward(self, x):
embedded = self.embedding_layer(x)
outputs, hidden = self.rnn_layer(embedded)
logits = self.linear_layer(hidden.squeeze(0))
return logits
모델과 데이터 파이프라인이 준비되었으니, 이들을 엮어서 실제 학습을 진행시키는 지휘자가 필요합니다.
모델 학습의 전체 사이클을 관리하는 클래스나 함수를 포함합니다.
훈련 루프 (Train Loop)
: 정해진 에폭(Epoch)만큼 훈련을 반복합니다. 각 스텝마다 `DataLoader`로부터 미니배치를 받아 모델에 주입하고, 나온 예측값과 실제 정답을 비교하여 오차(Loss)를 계산합니다.
역전파 및 업데이트
: 계산된 오차를 바탕으로 역전파(`loss.backward()`)를 수행하여 각 가중치의 기울기를 계산하고, 옵티마이저(`optimizer.step()`)를 통해 모델의 가중치를 정답에 더 가까워지는 방향으로 업데이트합니다.
# text_classification/trainer.py
class Trainer:
def __init__(self, model, train_loader, optimizer, criterion):
self.model = model
self.train_loader = train_loader
self.optimizer = optimizer
self.criterion = criterion
def train(self, num_epochs):
for epoch in range(num_epochs):
self.model.train()
for batch_texts, batch_labels in self.train_loader:
self.optimizer.zero_grad()
logits = self.model(batch_texts)
loss = self.criterion(logits, batch_labels)
loss.backward()
self.optimizer.step()
위에서 만든 모든 부품들(Dataset, Model, Trainer)을 조립해서 실제로 학습을 시작하고,
학습된 모델로 새로운 데이터를 예측하는 실행 스크립트입니다.
데이터셋, 데이터로더, 모델, 옵티마이저 등 학습에 필요한 모든 객체를 생성하고,
`Trainer` 객체에 전달하여 `train()` 메소드를 호출함으로써 전체 학습 과정을 시작합니다.
# train.py
from text_classification.dataset import TextClassificationDataset
from text_classification.models.rnn import RNNClassifier
from text_classification.trainer import Trainer
from torch.utils.data import DataLoader
# 1. 데이터셋 및 DataLoader 생성
train_dataset = TextClassificationDataset(...)
train_loader = DataLoader(train_dataset, batch_size=64, ...)
# 2. 모델, 옵티마이저 등 생성
model = RNNClassifier(...)
optimizer = torch.optim.Adam(model.parameters())
criterion = nn.CrossEntropyLoss()
# 3. Trainer 생성 및 학습 시작
trainer = Trainer(model, train_loader, optimizer, criterion)
trainer.train(num_epochs=10)
학습이 완료되어 저장된 모델 가중치(`best_model.pth`)와 단어 사전을 불러옵니다. 새로운 문장이 들어오면 학습 때와 똑같은 방식으로 전처리하여 숫자 배열로 만든 뒤, 모델에 입력하여 분류 결과를 예측합니다.
# classify.py
import torch
# 1. 학습된 모델과 단어 사전 불러오기
model.load_state_dict(torch.load('best_model.pth'))
model.eval() # 예측 모드로 전환
# 2. 새로운 문장 전처리
new_sentence = "이 영화는 정말 감동적이었어요."
token_ids = preprocess(new_sentence, vocab)
input_tensor = torch.tensor([token_ids])
# 3. 모델로 예측 수행
logits = model(input_tensor)
prediction = torch.argmax(logits, dim=1).item()
print(f"분류 결과: {'긍정' if prediction == 1 else '부정'}")